/**************************************************************************** 
 * Meta，一个轻量级的静态分区Hypervisor 
 *
 * 版权(c) , 2022-
 *
 * 作者:
 *      Tupelo Shen <shenwanjiang2013@163.com>
 *
 * Meta是一个自由软件，你可以在遵循GNU GPLv2协议的条件下，重新修改并发布它。
 *
 ***************************************************************************/

#include <fences.h>
#include <vmm.h>
#include <string.h>

struct config* vm_config_ptr;

/**
 * 功能描述: VMM实例化
 * 入口参数: void
 * 出口参数: void
 * 返 回 值: void
 * 修改日期: 2022-09-21
 */
void vmm_init(void)
{
    /* 如果没有指定VM，则CPU核进入Idle状态 */
    if(vm_config_ptr->vmlist_size == 0){
        if(cpu.id == CPU_MASTER)
            INFO("No virtual machines to run.");
        cpu_idle();
    } 
    
    /* 设置ARMv8虚拟化扩展的硬件特性 */
    vmm_arch_init();

    /* 指定虚拟机过程中，使用的数据结构 */
    volatile static struct vm_assignment {
        spinlock_t  lock;
        bool        master;             /* VM的主核 */
        size_t      ncpus;              /* VM的核数 */
        cpumap_t    cpus;               /* 核的位表示 */
        pte_t       vm_shared_table;    /* 共享表 */
    } * vm_assign;

    size_t vmass_npages = 0;
    if (cpu.id == CPU_MASTER) {
        /* IO内存初始化 */
        iommu_init();

        vmass_npages =
            ALIGN(sizeof(struct vm_assignment) * vm_config_ptr->vmlist_size,
                  PAGE_SIZE) /
            PAGE_SIZE;
        vm_assign = mem_alloc_page(vmass_npages, SEC_HYP_GLOBAL, false);
        if (vm_assign == NULL) ERROR("cant allocate vm assignemnt pages");
        memset((void*)vm_assign, 0, vmass_npages * PAGE_SIZE);
    }

    cpu_sync_barrier(&cpu_glb_sync);

    bool master = false;
    bool assigned = false;
    vmid_t vm_id = 0;
    struct vm_config *vm_config = NULL;

    /**
     * 根据VM的亲和力设置CPU核
     * 1. 第一个核指定为master核
     * 2. 虚拟机ID等于在配置信息中的顺序
     * 3. 如果是辅核，在不设置master变量
     */
    for (size_t i = 0; i < vm_config_ptr->vmlist_size && !assigned; i++) {
        if (vm_config_ptr->vmlist[i].cpu_affinity & (1UL << cpu.id)) {
            spin_lock(&vm_assign[i].lock);
            if (!vm_assign[i].master) {
                vm_assign[i].master = true;
                vm_assign[i].ncpus++;
                vm_assign[i].cpus |= (1UL << cpu.id);
                master = true;
                assigned = true;
                vm_id = i;
            } else if (vm_assign[i].ncpus <
                       vm_config_ptr->vmlist[i].platform.cpu_num) {
                assigned = true;
                vm_assign[i].ncpus++;
                vm_assign[i].cpus |= (1UL << cpu.id);
                vm_id = i;
            }
            spin_unlock(&vm_assign[i].lock);
        }
    }

    cpu_sync_barrier(&cpu_glb_sync);

    /**
     * 如果通过VM的亲和力绑定核失败，则直接绑定
     */
    if (assigned == false) {
        for (size_t i = 0; i < vm_config_ptr->vmlist_size && !assigned; i++) {
            spin_lock(&vm_assign[i].lock);
            if (vm_assign[i].ncpus <
                vm_config_ptr->vmlist[i].platform.cpu_num) {
                if (!vm_assign[i].master) {
                    vm_assign[i].master = true;
                    vm_assign[i].ncpus++;
                    master = true;
                    assigned = true;
                    vm_assign[i].cpus |= (1UL << cpu.id);
                    vm_id = i;
                } else {
                    assigned = true;
                    vm_assign[i].ncpus++;
                    vm_assign[i].cpus |= (1UL << cpu.id);
                    vm_id = i;
                }
            }
            spin_unlock(&vm_assign[i].lock);
        }
    }

    cpu_sync_barrier(&cpu_glb_sync);

    /**
     * 如果绑定核成功，则为VM分配内存：
     * 1. 主核负责分配内存
     * 2. 辅核只需将该段内存加入到共享页表即可
     */
    if (assigned) {
        vm_config = &vm_config_ptr->vmlist[vm_id];
        if (master) {
            size_t vm_npages = NUM_PAGES(sizeof(struct vm));
            /* VM地址空间为VM镜像分配内存 */
            vaddr_t va = mem_alloc_vpage( &cpu.as,
                                        SEC_HYP_VM,
                                        (vaddr_t)META_VM_BASE,
                                        vm_npages);
            mem_map(&cpu.as, va, NULL, vm_npages, PTE_HYP_FLAGS);
            memset((void*)va, 0, vm_npages * PAGE_SIZE);
            fence_ord_write();
            /* 获取VM镜像地址，作为共享页表，因为VM的每个核都会访问这块区域 */
            vm_assign[vm_id].vm_shared_table =
                    *pt_get_pte(&cpu.as.pt, 0, (vaddr_t)META_VM_BASE);
        } else {
            while (vm_assign[vm_id].vm_shared_table == 0);
            pte_t* pte = pt_get_pte(&cpu.as.pt, 0, (vaddr_t)META_VM_BASE);
            *pte = vm_assign[vm_id].vm_shared_table;
            fence_sync_write();
        }
    }

    cpu_sync_barrier(&cpu_glb_sync);

    /* 主核负责释放`vm_assign`这个临时变量 */
    if (cpu.id == CPU_MASTER) {
        mem_free_vpage(&cpu.as, (vaddr_t)vm_assign, vmass_npages, true);
    }

    ipc_init(vm_config, master);

    if (assigned) {
        vm_init((struct vm*)META_VM_BASE, vm_config, master, vm_id);
        vcpu_run(cpu.vcpu);
    } else {
        cpu_idle();
    }
}